import { HttpException, HttpStatus, Injectable } from '@nestjs/common'
import { InjectRepository } from '@nestjs/typeorm'
import { Like, Repository } from 'typeorm'
import { join } from 'path'
import { writeFile } from 'fs'
import { ensureDirSync } from 'fs-extra'

import { I_Response } from '../../types/type'
import {
  PostEntity,
  UserEntity,
  PostCommentEntity,
  CategoryEntity,
  UploadEntity
} from '../../entitys'

import {
  PostQueryPage,
  PostDto,
  PostSearchQueryPage,
  SetComment
} from './dtos/Response.dto'
import { QueryPage } from '../apiProperty'

const PUBLIC_PATCH = process.cwd() + '/public'

import * as dayjs from 'dayjs'
import 'dayjs/locale/zh-cn'
dayjs.locale('zh-cn')

@Injectable()
export class PostService {
  constructor(
    @InjectRepository(PostEntity)
    private readonly postRepository: Repository<PostEntity>,
    @InjectRepository(UserEntity)
    private readonly userRepository: Repository<UserEntity>,
    @InjectRepository(PostCommentEntity)
    private readonly comment: Repository<PostCommentEntity>,
    @InjectRepository(CategoryEntity)
    private readonly category: Repository<CategoryEntity>,
    @InjectRepository(UploadEntity)
    private readonly upload: Repository<UploadEntity>
  ) {
    this.caretCategory()
  }

  async uploadFiles(file: Express.Multer.File): Promise<I_Response<unknown>> {
    try {
      const fileType = file.mimetype.split('/')[1] || ''
      let filePath = ''

      const time = dayjs(new Date()).format('YYYY-MM-DD')

      if (['jpg', 'jpeg', 'jiff', 'png', 'gif'].indexOf(fileType) > -1) {
        ensureDirSync(join(PUBLIC_PATCH, `/uploads/image/${time}`))
        filePath = `/uploads/image/${time}/IMG${Date.now().toString(
          16
        )}.${fileType}`
      } else if (['mp4', 'mp3', 'avi', 'rmvb'].indexOf(fileType) > -1) {
        ensureDirSync(join(PUBLIC_PATCH, `/uploads/media/${time}`))
        filePath = `/uploads/media/${time}/MEDIA${Date.now().toString(
          16
        )}.${fileType}`
      } else {
        throw new HttpException('未知的文件格式', HttpStatus.BAD_REQUEST)
      }

      writeFile(
        join(PUBLIC_PATCH + filePath),
        Buffer.from(file.buffer.toString('base64'), 'base64'),
        err => {
          return {
            message: '文件上传失败 => ' + err,
            code: 400,
            success: false
          }
        }
      )

      const fileToSaved = new UploadEntity()
      fileToSaved.url = filePath
      const { url } = await this.upload.save(fileToSaved)
      return { data: url, message: '文件上传成功' }
    } catch (error) {
      console.log('err', error.message)
      throw new HttpException(
        '文件上传失败 => ' + error.message,
        HttpStatus.INTERNAL_SERVER_ERROR
      )
    }
  }

  async caretCategory() {
    try {
      if ((await this.category.find()).length >= 20) return
      const categoryList: CategoryEntity[] = []
      const cate = Object.assign(new CategoryEntity(), {
        id: 999,
        name: '头条',
        isTop: 1
      })
      categoryList.push(cate)
      const arr = [
        '热点',
        '娱乐',
        '体育',
        '财经',
        '汽车',
        '军事',
        '男人',
        '视频',
        '科技',
        '手机',
        '女人',
        '数码',
        '段子',
        '时尚',
        '游戏',
        '教育',
        '健康',
        '旅游',
        '房产'
      ]
      for (let i = 0, cc: CategoryEntity; i < 19; i++) {
        cc = Object.assign(new CategoryEntity(), {
          id: i + 1,
          name: arr[i],
          isTop: i < 8 ? 1 : 0
        })
        categoryList.push(cc)
      }

      await this.category
        .createQueryBuilder()
        .insert()
        .into(CategoryEntity)
        .values(categoryList)
        .execute()
    } catch (error) {
      console.log('err', error)
      throw new HttpException(error.message, HttpStatus.INTERNAL_SERVER_ERROR)
    }
  }

  async savePost(body: PostDto, post: PostEntity) {
    try {
      const category = (
        await this.category.find({ relations: ['post'] })
      ).filter(item => body.category?.includes(item.id))
      const cover = (await this.upload.find()).filter(item =>
        body.cover?.includes(item.url)
      )
      post.cover = cover
      post.category = category
      const postEntity = await this.postRepository.save(post)
      const data = await this.postRepository.findOne({
        where: { id: postEntity.id },
        relations: { category: true }
      })
      return data
    } catch (error) {
      console.log('err', error)
      return null
    }
  }

  async caretPost(id: string, body: PostDto): Promise<I_Response<unknown>> {
    const post: PostEntity = {
      ...new PostEntity(),
      title: body.title as string,
      content: (body.content as string)?.replace(/script/gi, 'code'),
      type: body.type || 1
    }
    try {
      const user = await this.userRepository.findOne({
        where: { id },
        relations: { post: true }
      })
      post.user = user
      const data = await this.savePost(body, post)
      return { data }
    } catch (error) {
      console.log('error.message,', error.message)
      throw new HttpException(error.message, HttpStatus.INTERNAL_SERVER_ERROR)
    }
  }

  async updatePost(id: string, body: PostDto): Promise<I_Response<unknown>> {
    const base = {
      title: true,
      content: true,
      type: true,
      cover: true
    }
    const time = new Date(Date.now() + 1000 * 60 * 60 * 8).toISOString()
    const update = {
      updateAt: time.substring(0, time.indexOf('.'))?.replace(/t/gi, ' ')
    }

    for (const key in body) {
      if (Object.prototype.hasOwnProperty.call(body, key) && base[key]) {
        update[key] =
          key === 'content'
            ? (body[key] as string)?.replace(/script/gi, 'code')
            : body[key]
      }
    }

    try {
      const post = await this.postRepository.findOne({
        where: { id },
        relations: { category: true, cover: true }
      })
      if (!post?.id) {
        return {
          code: 400,
          message: '编辑文章失败，文章不存在',
          success: false
        }
      }
      const data = await this.savePost(body, { ...post, ...update })
      if (data?.id) {
        return { data, message: '修改成功' }
      }
      return { success: false, code: 400, message: '修改失败' }
    } catch (error) {
      console.log('err', error.message)
      throw new HttpException(error.message, HttpStatus.INTERNAL_SERVER_ERROR)
    }
  }

  async getPost(query: PostQueryPage): Promise<I_Response<unknown>> {
    try {
      let post: [PostEntity[], number] = [[], 0]
      const { pageIndex = '1', pageSize = '10', category = '0' } = query
      const cid = parseInt(category)
      const [index, size] = [parseInt(pageIndex) - 1, parseInt(pageSize)]
      if (!cid) {
        post = await this.postRepository.findAndCount({
          skip: index * size,
          take: size,
          relations: { category: true, comments: true, cover: true, user: true }
        })
      } else {
        const lastArg: Record<string, number> =
          cid && cid !== 999 ? { categoryId: cid } : {}
        const threeArg = cid && cid !== 999 ? 'category.id = :categoryId' : ''
        post = await this.postRepository
          .createQueryBuilder('post')
          .where('post.open = :open', { open: 1 })
          .innerJoinAndSelect('post.category', 'category', threeArg, lastArg)
          .leftJoinAndSelect('post.comments', 'comments')
          .leftJoinAndSelect('post.cover', 'cover')
          .leftJoinAndSelect('post.user', 'user')
          .skip(index * size)
          .take(size)
          .getManyAndCount()
      }
      const postList = post[0].map(({ comments, ...post }) => {
        post.commentsLength = comments.length
        return post
      })
      return { data: { post: postList, count: post[1] } }
    } catch (error) {
      console.log('err', error.message)
      throw new HttpException(error.message, HttpStatus.INTERNAL_SERVER_ERROR)
    }
  }

  async findPostById(
    id: string,
    uid: string = null
  ): Promise<I_Response<unknown>> {
    try {
      const { likeUsers, comments, ...post } =
        await this.postRepository.findOne({
          where: { id },
          relations: ['category', 'comments', 'user', 'likeUsers', 'cover']
        })
      const data = {
        ...post,
        likeLength: 0,
        commentsLength: 0,
        hasFollow: false,
        hasStar: false,
        hasLike: false
      }
      data.commentsLength = comments?.length
      data.likeLength = likeUsers.length
      data.hasLike = likeUsers.some(item => item.id === uid)

      if (uid) {
        const user = await this.userRepository.findOne({
          where: { id: uid },
          relations: ['follows', 'likePosts']
        })
        data.hasStar = user.likePosts.some(item => item.id === id)
        data.hasFollow = user.follows.some(item => item.id === post.user.id)
      }
      const findPostId = post?.id
      return {
        code: !findPostId ? 400 : 200,
        message: !findPostId ? '商品不存在' : '获取成功',
        success: !!findPostId,
        data
      }
    } catch (error) {
      console.log('err', error.message)
      throw new HttpException(error.message, HttpStatus.INTERNAL_SERVER_ERROR)
    }
  }

  async findPostByKeyword(
    query: PostSearchQueryPage
  ): Promise<I_Response<unknown>> {
    try {
      const { pageIndex = '1', pageSize = '10', keyword = '' } = query
      const [index, size] = [parseInt(pageIndex) - 1, parseInt(pageSize)]
      const post = await this.postRepository
        .createQueryBuilder('post')
        .where('post.title like :keyword', { keyword: `%${keyword}%` })
        .andWhere('post.open = :open', { open: 1 })
        .leftJoinAndSelect('post.comments', 'comments')
        .leftJoinAndSelect('post.cover', 'cover')
        .leftJoinAndSelect('post.user', 'user')
        .skip(index * size)
        .take(size)
        .getManyAndCount()
      const postList = post[0].map(({ comments, ...post }) => {
        post.commentsLength = comments.length
        return post
      })
      return { data: { post: postList, count: post[1] } }
    } catch (error) {
      console.log('err', error.message)
      throw new HttpException(error.message, HttpStatus.INTERNAL_SERVER_ERROR)
    }
  }

  async findPostRecommend(
    query: PostSearchQueryPage
  ): Promise<I_Response<unknown>> {
    try {
      const { pageIndex = '1', pageSize = '10', keyword = '' } = query
      const [index, size] = [parseInt(pageIndex) - 1, parseInt(pageSize)]
      const post = await this.postRepository.find({
        where: { title: Like(`%${keyword}%`) },
        skip: index * size,
        take: size
      })
      return { data: post }
    } catch (error) {
      console.log('err', error.message)
      throw new HttpException(error.message, HttpStatus.INTERNAL_SERVER_ERROR)
    }
  }

  async getComment(id: string, query: QueryPage): Promise<I_Response<unknown>> {
    try {
      const { pageIndex = '1', pageSize = '10' } = query
      const [index, size] = [parseInt(pageIndex) - 1, parseInt(pageSize)]
      const post = await this.postRepository.findOneBy({ id })
      const comment = await this.comment.find({
        where: { post },
        skip: index * size,
        take: size
      })
      return { data: comment }
    } catch (error) {
      console.log('err', error.message)
      throw new HttpException(error.message, HttpStatus.INTERNAL_SERVER_ERROR)
    }
  }

  async setComment(
    userId: string,
    postId: string,
    body: SetComment
  ): Promise<I_Response<unknown>> {
    try {
      const { parentId = null, content = null } = body
      const user = await this.userRepository.findOneBy({ id: userId })
      const post = await this.postRepository.findOneBy({ id: postId })
      const parent = await this.comment.findOneBy({ id: parentId, post })
      const newComment = new PostCommentEntity()
      newComment.content = content
      newComment[parentId && parent ? 'parent' : Symbol.for('parent')] = parent
      newComment.post = post
      newComment.user = user
      const res = await this.comment.save(newComment)
      return { data: res }
    } catch (error) {
      console.log('err', error.message)
      throw new HttpException(error.message, HttpStatus.INTERNAL_SERVER_ERROR)
    }
  }

  async starPost(userId: string, postId: string): Promise<I_Response<unknown>> {
    try {
      const user = await this.userRepository.findOne({
        where: { id: userId },
        relations: ['starPosts']
      })
      const post = await this.postRepository.findOneBy({ id: postId })
      const star = user.starPosts.some(v => v.id === post.id)
      if (star) {
        user.starPosts = user.starPosts.filter(v => v.id !== postId)
      } else {
        user.starPosts.push(post)
      }
      const time = new Date(Date.now() + 1000 * 60 * 60 * 8).toISOString()
      user.updateAt = time.substring(0, time.indexOf('.'))?.replace(/t/gi, ' ')
      await this.userRepository.save(user)
      return { message: star ? '已取消收藏' : '收藏成功' }
    } catch (error) {
      console.log('err', error.message)
      throw new HttpException(error.message, HttpStatus.INTERNAL_SERVER_ERROR)
    }
  }

  async likePost(userId: string, postId: string): Promise<I_Response<unknown>> {
    try {
      const user = await this.userRepository.findOne({
        where: { id: userId },
        relations: ['likePosts']
      })
      const post = await this.postRepository.findOneBy({ id: postId })
      const star = user.likePosts.some(v => v.id === post.id)
      if (star) {
        user.likePosts = user.likePosts.filter(v => v.id !== postId)
      } else {
        user.likePosts.push(post)
      }
      const time = new Date(Date.now() + 1000 * 60 * 60 * 8).toISOString()
      user.updateAt = time.substring(0, time.indexOf('.'))?.replace(/t/gi, ' ')
      await this.userRepository.save(user)
      return { message: star ? '已取消点赞' : '点赞成功' }
    } catch (error) {
      console.log('err', error.message)
      throw new HttpException(error.message, HttpStatus.INTERNAL_SERVER_ERROR)
    }
  }
}
